/* $Id: message.c,v 1.3 2006/03/15 03:49:16 jfb Exp $ */
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/string.h>

#include "msg.h"
#include "conn.h"
#include "date.h"
#include "http.h"
#include "entity.h"
#include "private.h"


/* keep this ordered according to the enum http_method entries */
const char *http_req_methods[8] = {
	"CONNECT",
	"DELETE",
	"GET",
	"HEAD",
	"OPTIONS",
	"POST",
	"PUT",
	"TRACE",
};



static ssize_t  http_msg_parsereq  (const void *, size_t, http_msg_t *);
static ssize_t  http_msg_parseresp (const void *, size_t, http_msg_t *);
static ssize_t  http_msg_parsehdrs (const void *, size_t, http_msg_t *);
static ssize_t  http_msg_parsebody (const void *, size_t, http_msg_t *);


/*
 * http_msg_alloc()
 *
 * Allocate a new HTTP message of type <type> and initialize it.  A `Date'
 * header is automatically generated with the time of creation of the
 * structure as its value.
 * Returns a pointer to the new structure on success, or NULL on failure.
 */
http_msg_t*
http_msg_alloc(enum http_msg_type type)
{
	http_msg_t *msg;
	char buf[32];

	if ((msg = cnp_malloc(sizeof(*msg))) == NULL) {
		http_log(CNP_LOG_ERRNO, "failed to allocate HTTP message");
		return (NULL);
	}
	memset(msg, 0, sizeof(*msg));

	msg->msg_type = type;

	time(&msg->msg_crtime);

	TAILQ_INIT(&(msg->msg_headers));

	/* set the Date header */
	if (http_date_format(msg->msg_crtime, HTTP_DATE_DEFAULT, buf,
	    sizeof(buf)) == -1)
		http_log(CNP_LOG_WARN, "failed to format date");
	else if (http_msg_sethdr(msg, "Date", buf) == -1)
		http_log(CNP_LOG_WARN, "failed to set Date header");

	return (msg);
}


/*
 * http_msg_free()
 *
 * Free an HTTP message <msg> and all of its associated resources.
 */
void
http_msg_free(http_msg_t *msg)
{
	http_hdr_t *hdr;

	if (msg->msg_type == HTTP_MSG_REQ) {
		if (msg->msg_req.req_methodname != NULL)
			cnp_free(msg->msg_req.req_methodname);

		if (msg->msg_req.req_url != NULL)
			http_uri_free(msg->msg_req.req_url);

		if (msg->msg_req.req_conn != NULL)
			http_conn_free(msg->msg_req.req_conn);
	}
	else if (msg->msg_type == HTTP_MSG_RESP) {
	}

	while ((hdr = TAILQ_FIRST(&msg->msg_headers)) != NULL) {
		TAILQ_REMOVE(&msg->msg_headers, hdr, hdr_link);
		http_hdr_free(hdr);
	}

	if (msg->msg_entity != NULL)
		http_entity_free(msg->msg_entity);

	cnp_free(msg);
}


/*
 * http_msg_sethdr()
 *
 * Add a header to the list of headers for the message <msg>.  The
 * header's name is given in <name> and the associated value in <val>.
 * Returns 0 on success, or -1 on failure.
 */
int
http_msg_sethdr(http_msg_t *msg, const char *name, const char *val)
{
	char *copy;
	http_hdr_t *hdr;

	if ((hdr = http_msg_gethdr(msg, name)) == NULL) {
		if ((hdr = http_hdr_create(name, val)) == NULL)
			return (-1);

		TAILQ_INSERT_TAIL(&msg->msg_headers, hdr, hdr_link);
	} else {
		if ((copy = strdup(val)) == NULL) {
			http_log(CNP_LOG_ERRNO, "failed to copy header value");
			return (-1);
		}

		if (hdr->hdr_val != NULL)
			cnp_free(hdr->hdr_val);

		hdr->hdr_val = copy;
	}

	return (0);
}


/*
 * http_msg_rmhdr()
 *
 * Remove the header with name <name> from the list of headers
 * for the message <msg>.
 * Returns 0 on success, or -1 on failure.
 */
int
http_msg_rmhdr(http_msg_t *msg, const char *name)
{
	http_hdr_t *hdr;

	TAILQ_FOREACH(hdr, &msg->msg_headers, hdr_link) {
		if (strcmp(hdr->hdr_name, name) == 0) {
			TAILQ_REMOVE(&msg->msg_headers, hdr, hdr_link);
			http_hdr_free(hdr);
			break;
		}
	}

	return (0);
}

/*
 * http_msg_gethdr()
 *
 * Get the header whose name is <name> from the list of headers for the
 * message <msg>.
 */
http_hdr_t*
http_msg_gethdr(http_msg_t *msg, const char *name)
{
	http_hdr_t *hdr;

	TAILQ_FOREACH(hdr, &msg->msg_headers, hdr_link)
		if (strcmp(hdr->hdr_name, name) == 0)
			return (hdr);

	return (NULL);
}

/*
 * http_msg_mkenthdrs()
 *
 * Create all of the necessary entity-related headers based on the entity
 * attached to the HTTP message <msg>, as described in Section 7.1 of RFC 2616.
 * The following headers are currently set, if they have an associated value:
 *
 * - Allow
 * - Content-Encoding
 * - Content-Language
 * - Content-Length
 * - Content-Location
 * - Content-MD5
 * - Content-Range
 * - Content-Type
 * - Expires
 * - Last-Modified
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_msg_mkenthdrs(http_msg_t *msg)
{
	char buf[128];
	time_t lastmod;
	http_ent_t *ent = msg->msg_entity;

	if (ent == NULL) {
		http_log(CNP_LOG_WARN, "cannot make entity headers without a "
		    "valid entity");
		return (-1);
	}

	if (http_msg_sethdr(msg, "Content-Type", ent->ent_media) == -1) {
		http_log(CNP_LOG_WARN, "failed to set Content-Type");
		return (-1);
	}

	snprintf(buf, sizeof(buf), "%llu",
	    (unsigned long long)ent->ent_size);
	if (http_msg_sethdr(msg, "Content-Length", buf) < 0) {
		http_log(CNP_LOG_WARN, "failed to set Content-Length");
		return (-1);
	}

	if ((ent->ent_encoding != NULL) && (http_msg_sethdr(msg,
	    "Content-Encoding", ent->ent_encoding) == -1)) {
		http_log(CNP_LOG_WARN, "failed to set Content-Encoding");
		return (-1);
	}

	if (!(ent->ent_flags & HTTP_ENTITY_VOLATILE)) {
		/*
		 * Section 14.29 of RFC 2616 specifies that if the last
		 * modified timestamp on the resource is in the future, the
		 * creation time of the message should be used instead.
		 */
		lastmod = MIN(ent->ent_mtime, msg->msg_crtime);

		if (http_date_format(lastmod, HTTP_DATE_DEFAULT, buf,
		    sizeof(buf)) == -1) {
			http_log(CNP_LOG_WARN,
			    "failed to format Last-Modified date");
			return (-1);
		}
		else if (http_msg_sethdr(msg, "Last-Modified", buf) < 0) {
			http_log(CNP_LOG_WARN,
			    "failed to set Last-Modified date");
			return (-1);
		}
	}

	return (0);
}

/*
 * http_msg_format()
 *
 * Format the HTTP message <msg> into its HTTP string equivalent.  The result
 * is stored in <dst>, which is of size <dlen>.
 */
ssize_t
http_msg_format(http_msg_t *msg, void **dst, size_t *dlen, int flags)
{
	char tmp[256];
	ssize_t ret;
	size_t left, len = 0;
	uint8_t *dp;
	http_hdr_t *hdr;

	dp = *(uint8_t **)dst;
	left = *dlen;

	if (msg->msg_type == HTTP_MSG_REQ) {
		ret = (ssize_t)snprintf(dp, left, "%s %s HTTP/%u.%u" HTTP_CRLF,
		    http_req_methods[msg->msg_req.req_method + 1],
		    msg->msg_req.req_url->uri_path,
		    HTTP_VERSION_MAJOR, HTTP_VERSION_MINOR);
	}
	else {
		http_log(CNP_LOG_DEBUG, "formatting HTTP response");
		ret = (ssize_t)snprintf(dp, left, "HTTP/%u.%u %u %s" HTTP_CRLF,
		    HTTP_VERSION_MAJOR, HTTP_VERSION_MINOR,
		    msg->msg_resp.resp_code,
		    http_status_getdesc(msg->msg_resp.resp_code));
	}

	if (ret == -1) {
		http_log(CNP_LOG_ERRNO, "failed to format message");
		return (-1);
	}
	else if (ret >= (ssize_t)left) {
		/* buffer too small */
		if (!(flags & HTTP_MSG_DYNBUF)) {
			http_log(CNP_LOG_ERR, "destination buffer is too small "
			    "for message");
			return (-1);
		}
	}

	dp += (size_t)ret;
	len += (size_t)ret;
	left -= (size_t)ret;

	TAILQ_FOREACH(hdr, &msg->msg_headers, hdr_link) {
		ret = (ssize_t)snprintf(tmp, sizeof(tmp), "%s: %s" HTTP_CRLF,
		    hdr->hdr_name, hdr->hdr_val);
		if (ret == -1) {
			http_log(CNP_LOG_ERRNO, "failed to format message "
			    "header `%s'", hdr->hdr_name);
			return (-1);
		}
		else if (left < (size_t)ret) {
			if (!(flags & HTTP_MSG_DYNBUF)) {
				http_log(CNP_LOG_ERR, "destination buffer is "
				    "too small for message");
				return (-1);
			}

			/* grow buffer */
		}

		memcpy(dp, tmp, (size_t)ret);

		left -= (size_t)ret;
		dp += (size_t)ret;
		len += (size_t)ret;
	}

	memcpy(dp, HTTP_CRLF, 2);
	left -= 2;
	len += 2;

	return (len);
}

/*
 * http_msg_parse()
 *
 * Parse the contents of the HTTP message string <http_str> and create the
 * associated message structure.
 * Returns the message structure on success, or NULL on failure.
 */
http_msg_t*
http_msg_parse(const void *data, size_t len)
{
	const uint8_t *sp;
	size_t left;
	ssize_t ret;
	http_msg_t *msg;
	enum http_msg_type type = HTTP_MSG_REQ;

	left = len;
	sp = (const uint8_t *)data;

	if (strncmp((const char *)data, "HTTP", MIN(len, 4)) == 0)
		type = HTTP_MSG_RESP;

	if ((msg = http_msg_alloc(type)) == NULL)
		return (NULL);

	if (type == HTTP_MSG_REQ)
		ret = http_msg_parsereq(sp, left, msg);
	else
		ret = http_msg_parseresp(sp, left, msg);

	sp += (size_t)ret;
	left -= (size_t)ret;

	/* now we should be at the start of the HTTP-headers */
	ret = http_msg_parsehdrs(sp, left, msg);
	if (ret == -1) {
	}

	sp += (size_t)ret;
	left -= (size_t)ret;

	if (left > 0)
		ret = http_msg_parsebody(sp, left, msg);

	return (msg);
}

/*
 * http_msg_findeol()
 *
 * Find the next end of line (either a CRLF or a single LF) character from
 * the start of <data>, which is of size <len>.
 * Returns the offset from the start of <data> where the end of line starts,
 * or -1 if no end of line was found within the buffer.
 */
ssize_t
http_msg_findeol(const void *data, size_t len)
{
	const uint8_t *sp, *ep;

	sp = (const uint8_t *)data;

	for (ep = sp; len > (size_t)(ep - sp); ep++) {
		if ((*ep == '\r') && ((size_t)(ep - sp) < (len - 1)) &&
		    (*(ep + 1) == '\n'))
			return (ssize_t)(ep - sp);

		/* XXX: tolerant code missing here */
	}

	return (-1);
}

/*
 * http_msg_parsehdrs()
 *
 * Parse the sequence of HTTP headers located after the request or response
 * line in the HTTP message.  The start of the headers is expected to be
 * found at <data>, which is of size <len>.  Headers found in the message
 * are added to the message structure <msg>.
 * Returns the number of bytes consumed as part of parsing, or -1 on failure.
 */
static ssize_t
http_msg_parsehdrs(const void *data, size_t len, http_msg_t *msg)
{
	const uint8_t *sp;
	size_t tlen, left = len;
	http_hdr_t *hdr, *ohdr;

	sp = (const uint8_t *)data;

	/* now we should be at the start of the HTTP-headers */
	while (left > 0) {
		if (*sp == '\r') {
			sp++;
			if (left > 1) {
				if (*sp == '\n') {
					sp++;
					break;
				}
			}
			else
				return (-1);
		}
		else if (*sp == '\n') {
			sp++;
			break;
		}

		tlen = left;
		if ((hdr = http_hdr_parse(sp, &tlen)) == NULL)
			return (-1);

		sp += tlen;
		left -= tlen;

		/* check for possible duplicate and erase it */
		TAILQ_FOREACH(ohdr, &msg->msg_headers, hdr_link) {
			if (strcmp(ohdr->hdr_name, hdr->hdr_name) == 0) {
				TAILQ_REMOVE(&msg->msg_headers, ohdr, hdr_link);
				http_hdr_free(ohdr);
				break;
			}
		}

		TAILQ_INSERT_TAIL(&msg->msg_headers, hdr, hdr_link);
	}

	return (sp - (const uint8_t *)data);
}

/*
 * http_msg_parsereq()
 *
 * Parse the contents of the HTTP request string <http_str> and create the
 * associated request structure.
 * Returns the request structure on success, or NULL on failure.
 */
static ssize_t
http_msg_parsereq(const void *data, size_t len, http_msg_t *msg)
{
	int v_major, v_minor;
	size_t urilen;
	unsigned int i;
	const uint8_t *sp, *ep;
	char uri[1024];
	http_req_t *req = &msg->msg_req;

	sp = (const uint8_t *)data;

	if ((ep = strpbrk(sp, HTTP_LWS)) == NULL) {
		http_log(CNP_LOG_ERR, "malformed HTTP request");
		return (-1);
	}

	for (i = 0; i < 8; i++) {
		if (strncmp(sp, http_req_methods[i], ep - sp) == 0) {
			req->req_method = (enum http_method)i + 1;
			break;
		}
	}

	if (req->req_method == HTTP_METHOD_INVAL) {
		http_log(CNP_LOG_ERR, "invalid HTTP method");
		return (-1);
	}

	sp = ep;
	while (HTTP_MSG_ISLWS(*sp))
		sp++;

	if ((ep = strpbrk(sp, HTTP_LWS)) == NULL) {
		http_log(CNP_LOG_ERR,
		    "malformed URL, missing HTTP specification");
		return (-1);
	}

	urilen = ep - sp;

	if (urilen >= sizeof(uri)) {
		http_log(CNP_LOG_ERR, "URI is too long");
		return (-1);
	}
	strncpy(uri, sp, urilen);
	uri[urilen] = '\0';

	if ((req->req_url = http_uri_parse(uri)) == NULL) {
		http_log(CNP_LOG_ERR, "failed to parse URL `%s'", uri);
		return (-1);
	}

	sp = ep;
	while (HTTP_MSG_ISLWS(*sp))
		sp++;

	if (sscanf(sp, "HTTP/%u.%u\r\n", &v_major, &v_minor) != 2) {
		http_log(CNP_LOG_ERR, "failed to parse HTTP specification");
		return (-1);
	}

	if (v_major == 0) {
		if (v_minor < 9) {
			http_log(CNP_LOG_WARN,
			    "unsupported HTTP version %u.%u, using 0.9",
			    v_major, v_minor);
		}
		msg->msg_version = HTTP_VERSION_09;
	} else if (v_major == 1) {
		if (v_minor == 0) {
			msg->msg_version = HTTP_VERSION_10;
		} else if (v_minor == 1) {
			msg->msg_version = HTTP_VERSION_11;
		}
	} else {
		http_log(CNP_LOG_ERR, "unsupported HTTP version");
		return (-1);
	}

	ep = strpbrk(sp, HTTP_CRLF);
	if (ep == NULL) {
		http_log(CNP_LOG_ERR, "missing CRLF at end of method");
		return (-1);
	}

	sp = ep + 2;

	return (ssize_t)(sp - (const uint8_t *)data);
}

/*
 * http_msg_parseresp()
 *
 */
static ssize_t
http_msg_parseresp(const void *data, size_t len, http_msg_t *msg)
{

	return (0);
}

/*
 * http_msg_parsebody()
 *
 */
static ssize_t
http_msg_parsebody(const void *data, size_t len, http_msg_t *msg)
{

	return (0);
}
